퍼셉트론의 학습

앞장에서는 퍼셉트론을 이용해 간단한 분류 문제를 수행해 보았다. 퍼셉트론 신경망의 가중치 [eqn: W_ij]와 편향값 [eqn: b_i]를 어떻게 정하는 지에 대한 논의는 생략하고, 주어진 값을 단순히 이용하는 순방향(feedforward) 퍼셉트론 신경망을 다뤘다. 이번 장에서는 퍼셉트론의 학습에 대해 설명한다. 새로운 정보를 뇌가 학습하는 과정에서 신경세포 사이를 연결하는 시냅스의 강도가 변한다는 것이 뇌과학 분야의 연구로 잘 알려져 있다. 기계 학습 분야에서도 인공 신경망의 가중치와 편향값을 주어진 입력데이터를 활용해 적절히 부여하는 과정이 바로 ‘학습’이다. 이처럼 인공신경회로망의 학습과정은 실제의 뇌의 학습과정과 닮았다. 인공신경회로망의 학습은 방법에 따라 크게 둘로 나뉜다. 하나는 지도학습(supervised learning), 다른 하나는 비지도학습(unsupervised learning)이라 불린다. 이번 장에서는 퍼셉트론을 학습시키기 위해 지도학습의 과정을 따르게 된다. 인공신경망의 지도학습을 위해서는 먼저, 개개의 입력 정보마다 출력되어야 하는 정답이 주어져 있어야 한다. 주어진 입력 정보를 처리해서 신경망이 최종 출력하는 정보와 정답을 정량적으로 비교하고는, 이 둘의 차이를 줄여나가는 방향으로 신경망 가중치와 편향값을 변화시키게 된다.

퍼셉트론은 인공지능 분야의 발전 초기에 널리 쓰인 용어다. 퍼셉트론의 작동방식을 따르는 노드들이 복잡한 구조로 서로 연결된 것이 요즘의 인공신경망이다.

이 현상을 발견한 과학자의 이름을 따서, 헵의 규칙(Hebb’s rule)이라 부른다. 뇌과학 분야에서는 서로 함께 발화하는 신경세포 사이의 시냅스는 강하게 연결된다는 의미로 “fire together, wire together”라는 재밌게 헵의 규칙을 표현하기도 한다.

앞장에 등장했던, 간단한 두 층으로 구성된 퍼셉트론 신경회로망을 보자. 입력 정보가 담긴 입력 벡터 [eqn: vec x]는 연결망의 가중치가 담긴 행렬 [eqn: W]에 곱해지고([eqn: W cdot vec x]) 편향값 벡터가 더해진 후 출력 층에 벡터의 형태([eqn: vec v = W cdot vec x + vec b])로 전달된다. 이 벡터는 출력층에서 활성화 함수를 거쳐 최종 출력 벡터의 형태를 갖게 된다. 즉, [eqn: vec y = vecphi ( vec v ) = vec phi ( W cdot vec x + vec b )]이다. 앞장에서 얻은 식 [eqn: y_i = phi( v_i ) = phi( sum_j W_ij x_j + b_i )]를 벡터와 행렬을 이용해 적었을 뿐, 정확히 같은 식이다. 정보가 입력층에서 출력층을 향하는 방향으로만 전달되므로 순방향(feedforward) 정보 전달이라 할 수 있다.

다음으로는 퍼셉트론 신경회로망의 지도학습의 과정을 자세히 살펴보자. 먼저, 주어진 입력 정보 [eqn: vec x]에 해당하는 출력층의 정답 벡터를 [eqn: vecd]라 부르자. 지도학습에서 학습 데이터로 주어지는 것은 이처럼 여러 개의 [eqn: (vec x , vec d )]의 쌍이다. 정답과 출력 정보의 차이를 오차라고 하면 [eqn: vec e = vec d - vec y]가 되는데, 이를 이용해 전체 오차함수 [eqn: E = vec e cdot vec e = sum_i e_i ^2 = sum_i (d_i - y_i )^2]를 적을 수 있다. 이 오차함수는 곡선 맞춤의 최소제곱법에도 등장했던 꼴이어서, 오차함수 [eqn: E]를 줄여가는 퍼셉트론의 학습은 앞에서 배운 경사하강법을 이용할 수 있다. 퍼셉트론 신경회로망의 오차함수는 [eqn: W]와 [eqn: vec b]의 함수이므로, [eqn: E(W , vec b )]의 형태로 적을 수 있는데, [eqn: E]가 줄어드는 방향으로 [eqn: W]와 [eqn: vec b]를 한 단계 한 단계 경사하강법을 이용해 변화시키면 된다. 즉, [eqn: W` RARROW `W- alpha {Partial E} over {Partial W}], [eqn: {vec{b}} ` RARROW ` {vec{b}} - alpha {Partial E} over {Partial {vec{b}}}]를 이용하면 된다. 이 식에 등장하는 경사값(gradient)을 구하면, [eqn: {Partial E} over {Partial W} =2 {vec{e}} CDOT {Partial {vec{e}}} over {Partial W} =`-2 {vec{e}} CDOT {Partial {vec{y}}} over {Partial W} =`-2 {vec{e}} CDOT {vec{phi prime }} ( {vec{v}} ) {Partial {vec{v}}} over {Partial W}], [eqn: {Partial E} over {Partial {vec{b}}} =`-2 {vec{e}} CDOT {Partial {vec{y}}} over {Partial {vec{b}}} =`-2 {vec{e}} CDOT {vec{phi prime }} ( {vec{v}} ) {Partial {vec{v}}} over {Partial {vec{b}}}]이다. 위의 식을 벡터와 행렬의 구성 성분별로 다시 적으면 [eqn: {Partial E} over {Partial W _{ij}} =-2 sum _{k} ^{} e _{k} phi prime (v _{k} ) delta _{ki} `x _{j} = -2 e_i phi prime(v_i ) x_j], [eqn: {Partial E} over {Partial b _{i}} =`-2 sum _{k} ^{} e_k {Partial y _{k}} over {Partial b _{i}} =`-2 sum _{k} ^{} e_k phi prime (v _{k} ) delta_ki = -2 e_i phi prime (v_i )]를 얻는다. 지금까지의 계산으로부터 지도학습을 통해 오차함수 [eqn: E]를 경사하강법을 이용해 줄여가는 [eqn: W_ij]와 [eqn: b_i]를 어떻게 변화시켜야 할 지를 알 수 있다. 즉,[eqn: W _{ij} ` RARROW `W _{ij} + alpha `e _{i} phi prime (v _{i} )x _{j} `,``b _{i} ` RARROW ``b _{i} + alpha `e _{i} phi prime (v _{i} )]를 이용하면 된다. 여기서 [eqn: alpha]는 학습률(learning rate)이라 불리는 값으로서, 학습의 속도를 조정하는 조절변수이다.

지금까지의 논의에서 주어진 정답과 퍼셉트론 신경회로망의 출력이 일치하는 방향으로 어떻게 가중치와 편향값을 조절해 나가야 하는 지를 알 수 있었다. 얻어진 학습규칙을 보면, 활성화 함수 [eqn: phi (v )]의 미분 [eqn: phi prime (v)]이 등장하는데, 만약 미분값이 0이라면 위에서 얻은 학습규칙을 따라서 신경회로망을 학습시키는 것은 불가능하다는 것을 알 수 있다. 앞장에서 계산의 편의상 생각했던 헤비사이드 계단함수가 바로 이런 경우다. 0이 아닌 모든 [eqn: x]에 대해서 계단함수는 미분값이 0이기 때문이다. 학습이 가능한 퍼셉트론 신경회로망을 구축하기 위해 여러 활성화 함수를 이용할 수 있는데, 먼저 가장 널리 쓰이는 것 중 하나인 시그모이드(sigmoid)함수 [eqn: phi (v)=1/(1+e ^{-v} )]를 택해보자.  시그모이드 함수의 미분을 구해보면, 흥미로운 관계식을 얻을 수 있다. 즉,  [eqn: phi prime (v) = {- 1} over {(1 + e^-v )^2 } cdot (-e^-v ) = 1 over { 1 + e^-v } cdot {e^-v } over {1 + e^-v } = phi(v) cdot [1 - phi(v) ]]를 만족하고, 또 [eqn: phi(v_i ) = y _i]이므로, 활성화함수가 시그모이드 함수일 때의 학습 규칙은 다음과 같이 적을 수 있다.

[eqn: {cases{W _{ij}&RARROW `W _{ij} + alpha `e _{i} `y _{i} (1-y _{i} )x _{j} ,#`````b _{i}&RARROW ``b _{i} + alpha `e _{i} y _{i} (1-y _{i} ) .}} &]

지금까지 설명한 내용을 바탕으로 앞장에서 살펴본 AND연산을 구현하는 퍼셉트론을 학습시키는 프로그램을 작성해보자. 출력층의 노드는 딱 하나만이 필요하므로, 위의 학습규칙을 더 간단히 적을 수 있다: [eqn: W _{j} RARROW `W _{j} + alpha `ey(1-y)x _{j} ,```b RARROW ``b + alpha `e y (1-y).] 학습에 이용할 수 있는 입력 벡터는 (1,1), (1,0), (0,1), (0,0) 이며, 각각의 경우의 정답을 적으면, 1, 0, 0, 0이 된다. 프로그램을 시작할 때 가중치와 편향값은 적당한 범위의 마구잡이 값으로 하고, 위에서 얻은 학습규칙을 따라 조금씩 학습률 [eqn: alpha]를 이용해 변화시키면 된다. 이 과정은 당연히 여러 번 반복해야 하는데, 반복 횟수를 기계학습 분야에서는 epoch의 숫자라고 부른다. 아래의 프로그램에서는 전체 epoch의 숫자(Nep)를 1000으로 했으며, 학습률(alpha)로는 0.5를 택했다. 프로그램의 결과로 epoch가 진행되면서 오차함수가 줄어드는 모습을 그래프로 그렸다.

import numpy as np, matplotlib.pyplot as plt

N = 4 # N = number of training data

Nep = 1000 # number of epochs to repeat

alpha = 0.5 # learning rate

def phi(x):

return 1.0/(1.0 + np.exp(-x))

x_train = np.array( [[1,1], [1,0], [0,1], [0,0]] ) # training set

d_train = np.array( [1, 0, 0 ,0]) # answer

e = np.zeros(4)

W = np.random.random( (1, 2)) # random weights

b = np.random.rand()

xarr = []

yarr = []

for ep in range(Nep):

for n in range(N):

x = x_train[n]

d = d_train[n]

v = np.sum(W*x) + b

y = phi(v)

e = d - y

W = W + alpha*e*y*(1-y)*x

b = b + alpha*e*y*(1-y)

if (ep % 10 == 0):

xarr.append(ep)

yarr.append(e*e)

for n in range(N):

v = np.sum(W*x_train[n]) + b

y = phi(v)

print(x_train[n], d_train[n], y)

print(W, b)

plt.plot(xarr,yarr)

plt.show()

== 예제 1==

위 프로그램에서 얻어진 [eqn: W _{1} , W _{2}, b]를 이용하면 AND 연산이 올바로 구현되는 이유를 설명하시오.

== 예제 2==

출력층의 노드를 한 개가 아닌 두 개로 해서 AND를 구현하는 퍼셉트론 신경회로망을 구현하려 한다. 입력 (1,1), (1,0), (0,1), (0,0)에 대해 출력층의 정답을 (1,0), (0,1), (0,1), (0,1)로 하는 신경회로망을 학습시키고 실행하는 프로그램을 작성하시오.

== 예제 3==

논리합(OR)과 배타적 논리합(XOR)을 학습하는 퍼셉트론 신경회로망을 구현하려 한다. 출력 노드가 각각 1개와 2개인 프로그램을 각각 작성하시오(예제 2 참조). OR은 잘 구현되는데 비해, XOR은 제대로 구현되지 않는 이유를 설명하시오.

위의 예제에서 살펴본 바와 같이, 그리고 앞장에서 설명한 바와 같이 배타적 논리합(XOR)은 단순히 입력층과 출력층이 있는 퍼셉트론 신경회로망으로는 구현할 수가 없다. 사실, 바로 이 XOR문제가 인공지능 분야에서 오랫동안 발전을 가로막았던 문제다. 결국, XOR문제를 해결한 것은 인공신경회로망에 은닉층을 입력층과 출력층 사이에 두는 방법이었다.

입력층과 출력층만으로 구성된 퍼셉트론 신경망을 단층 신경망이라 부른다. 둘 사이에 은닉층(hidden layer)을 두는 경우는 다층 신경망이라 하는데, 요즘의 심층 신경망(deep neural network)은 많은 수의 은닉층을 이용한다.

다음에는 은닉층이 있는 경우 어떻게 인공신경회로망을 학습시킬 수 있는 지를 설명해 보겠다. 입력층 노드의 정보 [eqn: x_i]는 입력층과 은닉층을 연결하는 가중치 행렬 [eqn: W]를 거쳐서 은닉층에 전달되어 [eqn: v_i = sum W_ij x_j + b_i]가 되고 이는 은닉층의 활성화 함수 [eqn: f]를 통해

[eqn: y_i = f(v_i )]로 변환되어 출력층에 전달된다. 은닉층과 출력층을 연결하는 가중치 행렬을 [eqn: V], 출력층의 편향값을 [eqn: c_i], 그리고 출력층의 활성화 함수를 [eqn: g]라 하자. 최종적으로 출력되는 출력층 노드의 출력값은 [eqn: z_i = g(s_i )]이며, 여기서 [eqn: s_i = sum V_ij y_j + c_j]이다. 그림은 순방향으로 정보가 입력층에서 은닉층을 거쳐 출력층으로 전달되는 것을 보여준다. 은닉층이 있는 경우에 각각의 가중치 행렬 [eqn: W, V]와 편향값 벡터 [eqn: vec b , vec c]를 학습하는 것은 은닉층이 없는 단순한 신경망과 수학적으로는 거의 동일하다. 오차함수는 정답 벡터 [eqn: vec d]와 최종 출력 벡터 [eqn: vec z]를 이용해 [eqn: E = sum_i (d_i - z_i )^2]로 정의할 수 있다. 일반적인 변수 [eqn: theta]에 대해 [eqn: {Partial E} over {Partial theta } ` PROPTO `- sum _{k} ^{} e _{k} {Partial z _{k}} over {Partial theta } =- sum _{k} ^{} e _{k} g prime (s _{k} ) {Partial s _{k}} over {Partial theta }]이다. 먼저 [eqn: theta = V_ij]로 하면, [eqn: {Partial E} over {Partial V _{ij}} ` PROPTO `- sum _{k} ^{} e _{k} g prime (s _{k} ) {Partial s _{k}} over {Partial V _{ij}} =- sum _{k} ^{} e _{k} g prime (s _{k} ) delta _{ki} y _{j} =-e _{i} g prime (s _{i} )y _{j}]를 얻는다. 또한, [eqn: theta = c_i]로 하면, [eqn: {Partial E} over {Partial c _{i}} ` PROPTO `-e _{i} g prime (s _{i} )]를 얻는다. 한편, [eqn: theta = W_ij]인 경우는 계산이 좀 더 복잡해져서, [eqn: {Partial E} over {Partial W _{ij}} ` PROPTO `- sum _{k} ^{} e _{k} g prime (s _{k} ) {Partial s _{k}} over {Partial W _{ij}}]이고, [eqn: {Partial s _{k}} over {Partial W _{ij}} = sum _{l} ^{} {Partial s _{k}} over {Partial y _{l}} {Partial y _{l}} over {Partial W _{ij}}], [eqn: {partial s_k } over {partial y_l } = V_kl], [eqn: {Partial y _{l}} over {Partial W _{ij}} = f prime (v _{l} ) {Partial v _{l}} over {Partial W _{ij}} = f prime (v _{l} )x _{j} delta _{li}]이다. 따라서, [eqn: {Partial E} over {Partial W _{ij}} ` PROPTO `- sum _{k} ^{} e _{k} g prime (s _{k} ) sum _{l} ^{} V _{kl} f prime (v _{l} )x _{j} delta _{li} = - sum _{k} ^{} e _{k} g prime (s _{k} ) V _{ki} f prime (v _{i} )x _{j}]를 얻는다. 마찬가지 계산을 [eqn: b_i]에 대해 하면, [eqn: {Partial E} over {Partial b _{i}} ` PROPTO `- sum _{k} ^{} e _{k} g prime (s _{k} ) {Partial s _{k}} over {Partial b _{i}}], [eqn: {Partial s _{k}} over {Partial b _{i}} = sum _{l} ^{} {Partial s _{k}} over {Partial y _{l}} {Partial y _{l}} over {Partial b _{i}}], [eqn: {Partial y _{l}} over {Partial b _{i}} =f prime (v _{l} ) {Partial v _{l}} over {Partial b _{i}} =f prime (v _{l} ) delta _{li}]이다. 따라서, [eqn: {Partial E} over {Partial b _{i}} ` PROPTO `- sum _{k} ^{} e _{k} g prime (s _{k} ) sum _{l} ^{} V _{kl} f prime (v _{l} ) delta _{li} =- sum _{k} ^{} e _{k} g prime (s _{k} )V _{ki} f prime (v _{i} )]를 얻는다. 지금까지의 계산에서 여러 번 등장한 [eqn: e _{i} g prime(s_i )]를 새로운 변수 [eqn: delta_ i equiv e_i g prime(s_i )]로 정의하면, 좀 더 간단한 형태로 적을 수 있다: [eqn: V _{ij} ` RARROW `V _{ij} + alpha ` delta _{i} y _{j} `,``c _{i} ` RARROW ``c _{i} + alpha delta _{i}], [eqn: W _{ij} ` RARROW `W _{ij} + alpha sum _{k} ^{} delta _{k} V _{ki} f prime (v _{i} )x _{j} `,``b _{i} ` RARROW ``b _{i} + alpha sum_k delta _{k} V_ki f prime (v_i )]. 세 번째와 네 번째 식의 [eqn: sum delta_k V_ki]는 행렬 [eqn: V]의 전치행렬인 [eqn: V^T]를 이용하면 [eqn: (V^T vec delta )_i]로 적을 수 있고, 변수 [eqn: epsilon_i equiv (V^T vec delta )_i f prime(v_i )]를 새로 정의하면, 최종적으로 아래의 학습규칙을 얻게 된다.

[eqn: V _{ij} ` RARROW `V _{ij} + alpha ` delta _{i} y _{j} `,``c _{i} ` RARROW ``c _{i} + alpha delta _{i}], [eqn: W _{ij} ` RARROW `W _{ij} + alpha epsilon_i x _{j} `,``b _{i} ` RARROW ``b _{i} + alpha epsilon_i] [단, [eqn: delta _{i} == e _{i} g prime (s _{i} ), epsilon _{i} =(V ^{T} vec delta ) _{i} f prime (v _{i} )]]

자, 이제 위를 이용해서 XOR을 하나의 은닉층을 이용해 구현해 보자. 은닉층과 출력층 모두 활성화 함수로는 시그모이드 함수를 이용하면, [eqn: delta_i = e_i g prime(s_i ) = e_i z_i (1-z_i ), epsilon_i = (V^T vec delta )_i y_i (1-y_i )]가 된다. 아래 프로그램에서는 은닉층의 노드의 수를 2, 출력층의 노드의 수를 1로 했다.

import numpy as np, matplotlib.pyplot as plt

N = 4 # N = number of training data

Nep = 4000 # number of epochs to repeat

alpha = 0.5 # learning rate

def phi(x):

return 1.0/(1.0 + np.exp(-x))

x_train = np.array( [[1,1], [1,0], [0,1], [0,0]] ) # training set

d_train = np.array( [0, 1, 1 ,0] ) # answer

W = np.random.random( (2, 2) ) # random weights

b = np.random.random( (2, 1) )

V = np.random.random( (1, 2) ) # random weights

c = np.random.rand()

xarr = []; yarr = []

for ep in range(Nep):

sume = 0

for n in range(N):

x = np.reshape(x_train[n], (2,1))

d = d_train[n]

v = W@x + b; y = phi(v)

s = V@y + c; z = phi(s)

e = d - z

sume += np.asscalar(e*e)

delta = z*(1-z)*e

e1 = V.T @ delta

epsil = y*(1-y)*e1

V += alpha*delta*y.T; c += alpha*delta

W += alpha*epsil*x.T; b += alpha*epsil

if (ep % 10 == 0):

xarr.append(ep); yarr.append(sume)

for n in range(N):

x = np.reshape(x_train[n], (2,1))

d = d_train[n]

v = W@x + b; y = phi(v)

s = V@y + c; z = phi(s)

print(n, x_train[n], d_train[n], z)

plt.plot(xarr,yarr)

plt.show()

== 과제 1 ==

출력층의 활성화 함수로 항등함수 [eqn: g(x) = x]를 이용하도록 위의 프로그램을 수정해 XOR을 구현하려 시도해 보시오.

== 과제 2 ==

출력층의 노드수를 2개로 해서 본문의 프로그램을 수정해 XOR을 구현하시오.